package io.github.annyylxyms.gynregistry.service;

import io.github.annyylxyms.gynregistry.cluster.Snapshot;
import io.github.annyylxyms.gynregistry.model.InstanceMeta;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

@Slf4j
public class GynRegistryService implements RegistryService {
    /**
     * 存所有的实例list
     */
    final static MultiValueMap<String, InstanceMeta> REGISTRY = new LinkedMultiValueMap<>();

    //版本和服务有关，和具体实例无关，所以用一个常规map即可
    //版本号变化具体有啥用？？
    final static Map<String, Long> VERSIONS = new ConcurrentHashMap<>();
    // 给服务加个时间戳，看30秒内有无版本变化？
    public final static Map<String, Long> TIMESTAMPS = new ConcurrentHashMap<>();
    //从递增数取版本号
    public final static AtomicLong VERSION = new AtomicLong(0);

    @Override
    public synchronized InstanceMeta register(String service, InstanceMeta instance) {
        List<InstanceMeta> metas = REGISTRY.get(service);
        if (metas != null && !metas.isEmpty()) {
            if (metas.contains(instance)) {
                log.info(" ===> instance {} already registred", instance.toUrl());
                instance.setStatus(true);
                return instance;
            }
        }

        log.info(" ===> register instance {}", instance.toUrl());
        REGISTRY.add(service, instance);
        instance.setStatus(true);
        Renew(instance, service);
        //注册\取消注册服务时刷新服务的版本号
        VERSIONS.put(service, VERSION.incrementAndGet());
        return instance;
    }

    @Override
    public synchronized InstanceMeta unregister(String service, InstanceMeta instance) {
        List<InstanceMeta> metas = REGISTRY.get(service);
        if (metas == null || metas.isEmpty()) {
            return null;
        }
        log.info(" ===> unregister instance {}", instance.toUrl());
        metas.removeIf(m -> m.equals(instance));
        instance.setStatus(false);
        Renew(instance, service);
        //注册\取消注册服务时刷新服务的版本号
        VERSIONS.put(service, VERSION.incrementAndGet());
        return instance;
    }

    @Override
    public List<InstanceMeta> getAllInstances(String service) {
        return REGISTRY.get(service);
    }

    @Override
    public synchronized long Renew(InstanceMeta instance, String... services) {
        //因为探活是针对instance的，时间戳的记录key要有instance
        long now = System.currentTimeMillis();
        for (String service : services) {
            TIMESTAMPS.put(service + "@" + instance.toUrl(), now);
        }
        //每30秒判断当前时间戳没过期，就让它保活
        return now;
    }

    @Override
    public Long version(String service) {
        return VERSIONS.get(service);
    }

    @Override
    public Map<String, Long> versions(String... services) {
        //toMap里三个参数：x->x（key不变），y->y（对应的取值），(a,b)->b（如果有重复的取后面的）
        return Arrays.stream(services).collect(Collectors.toMap(x -> x, VERSIONS::get, (a, b) -> b));
    }

    /**
     * synchronized防止第一句获取的REGISTRY时，VERSIONS等信息已经被修改了
     * 需要给数据修改方法（Renew、unregister、register），以及取快照方法都加上 synchronized，让他们互斥
     * 相当于深度拷贝的原子性操作，避免并发问题
     * @return
     */
    public static synchronized Snapshot snapshot() {
        LinkedMultiValueMap<String, InstanceMeta> registry = new LinkedMultiValueMap<>();
        registry.addAll(REGISTRY);
        Map<String, Long> versions = new HashMap<>(VERSIONS);
        Map<String, Long> timestamps = new HashMap<>(TIMESTAMPS);
        return new Snapshot(registry, timestamps, versions, VERSION.get());
    }

    public static synchronized long restore(Snapshot snapshot) {
        REGISTRY.clear();
        REGISTRY.addAll(snapshot.getREGISTRY());
        VERSIONS.clear();
        VERSIONS.putAll(snapshot.getVERSIONS());
        TIMESTAMPS.clear();
        TIMESTAMPS.putAll(snapshot.getTIMESTAMPS());
        VERSION.set(snapshot.getVersion());
        return snapshot.getVersion();
    }
}
